#pragma once

#include <string>
#include <vector>
#include <cassert>

#include "il2cpp-config.h"

#include "os/ErrorCodes.h"
#include "os/Atomic.h"
#include "os/Mutex.h"
#include "os/WaitStatus.h"
#include "utils/NonCopyable.h"

namespace il2cpp
{
namespace os
{

class SocketImpl;

enum AddressFamily
{
	kAddressFamilyError				= -1,
	kAddressFamilyUnspecified		= 0, // AF_UNSPEC
	kAddressFamilyUnix				= 1, // AF_UNIX
	kAddressFamilyInterNetwork		= 2, // AF_INET
	kAddressFamilyIpx				= 3, // AF_IPX
	kAddressFamilySna				= 4, // AF_SNA
	kAddressFamilyDecNet			= 5, // AF_DECnet
	kAddressFamilyAppleTalk			= 6, // AF_APPLETALK
	kAddressFamilyInterNetworkV6	= 7, // AF_INET6
	kAddressFamilyIrda				= 8, // AF_IRDA
};

enum SocketType
{
	kSocketTypeError		= -1,
	kSocketTypeStream		= 0, // SOCK_STREAM
	kSocketTypeDgram		= 1, // SOCK_DGRAM
	kSocketTypeRaw			= 2, // SOCK_RAW
	kSocketTypeRdm			= 3, // SOCK_RDM
	kSocketTypeSeqpacket	= 4, // SOCK_SEQPACKET
};

enum ProtocolType
{
	kProtocolTypeUnknown							= -1,
	kProtocolTypeIP									= 0,
	kProtocolTypeIcmp								= 1,
	kProtocolTypeIgmp								= 2,
	kProtocolTypeGgp								= 3,
	kProtocolTypeTcp								= 6,
	kProtocolTypePup								= 12,
	kProtocolTypeUdp								= 17,
	kProtocolTypeIdp								= 22,
	kProtocolTypeND									= 77,
	kProtocolTypeRaw								= 255,
	kProtocolTypeUnspecified						= 0,
	kProtocolTypeIpx								= 1000,
	kProtocolTypeSpx								= 1256,
	kProtocolTypeSpxII								= 1257,
	
// #if NET_1_1
	kProtocolTypeIPv6								= 41,
// #endif

// #if NET_2_0
	kProtocolTypeIPv4								= 4,
	kProtocolTypeIPv6RoutingHeader					= 43,
	kProtocolTypeIPv6FragmentHeader					= 44,
	kProtocolTypeIPSecEncapsulatingSecurityPayload	= 50,
	kProtocolTypeIPSecAuthenticationHeader			= 51,
	kProtocolTypeIcmpV6								= 58,
	kProtocolTypeIPv6NoNextHeader					= 59,
	kProtocolTypeIPv6DestinationOptions				= 60,
	kProtocolTypeIPv6HopByHopOptions				= 0,
// #endif
};

enum SocketFlags
{
	kSocketFlagsNone					= 0x00000000,
	kSocketFlagsOutOfBand				= 0x00000001,
	kSocketFlagsPeek					= 0x00000002,
	kSocketFlagsDontRoute				= 0x00000004,
	kSocketFlagsMaxIOVectorLength		= 0x00000010,
// #if NET_2_0
	kSocketFlagsTruncated				= 0x00000100,
	kSocketFlagsControlDataTruncated	= 0x00000200,
	kSocketFlagsBroadcast				= 0x00000400,
	kSocketFlagsMulticast				= 0x00000800,
// #endif
	kSocketFlagsPartial					= 0x00008000,
};

enum SocketOptionLevel
{
	kSocketOptionLevelSocket	= 65535,
	kSocketOptionLevelIP		= 0,
	kSocketOptionLevelTcp		= 6,
	kSocketOptionLevelUdp		= 17,

//#if NET_1_1
	kSocketOptionLevelIPv6		= 41,
//#endif
};

enum SocketOptionName
{
	kSocketOptionNameDebug					= 1,
	kSocketOptionNameAcceptConnection		= 2,
	kSocketOptionNameReuseAddress			= 4,
	kSocketOptionNameKeepAlive				= 8,
	kSocketOptionNameDontRoute				= 16,
	kSocketOptionNameBroadcast				= 32,
	kSocketOptionNameUseLoopback			= 64,
	kSocketOptionNameLinger					= 128,
	kSocketOptionNameOutOfBandInline		= 256,
	kSocketOptionNameDontLinger				= -129,
	kSocketOptionNameExclusiveAddressUse	= -5,
	kSocketOptionNameSendBuffer				= 4097,
	kSocketOptionNameReceiveBuffer			= 4098,
	kSocketOptionNameSendLowWater			= 4099,
	kSocketOptionNameReceiveLowWater		= 4100,
	kSocketOptionNameSendTimeout			= 4101,
	kSocketOptionNameReceiveTimeout			= 4102,
	kSocketOptionNameError					= 4103,
	kSocketOptionNameType					= 4104,
	kSocketOptionNameMaxConnections			= 2147483647,
	kSocketOptionNameIPOptions				= 1,
	kSocketOptionNameHeaderIncluded			= 2,
	kSocketOptionNameTypeOfService			= 3,
	kSocketOptionNameIpTimeToLive			= 4,
	kSocketOptionNameMulticastInterface		= 9,
	kSocketOptionNameMulticastTimeToLive	= 10,
	kSocketOptionNameMulticastLoopback		= 11,
	kSocketOptionNameAddMembership			= 12,
	kSocketOptionNameDropMembership			= 13,
	kSocketOptionNameDontFragment			= 14,
	kSocketOptionNameAddSourceMembership	= 15,
	kSocketOptionNameDropSourceMembership	= 16,
	kSocketOptionNameBlockSource			= 17,
	kSocketOptionNameUnblockSource			= 18,
	kSocketOptionNamePacketInformation		= 19,
	kSocketOptionNameNoDelay				= 1,
	kSocketOptionNameBsdUrgent				= 2,
	kSocketOptionNameExpedited				= 2,
	kSocketOptionNameNoChecksum				= 1,
	kSocketOptionNameChecksumCoverage		= 20,

// #if NET_2_0
	kSocketOptionNameHopLimit				= 21,
	kSocketOptionNameUpdateAcceptContext	= 28683,
	kSocketOptionNameUpdateConnectContext	= 28688,
// #endif

};

enum PollFlags
{
	kPollFlagsNone	= 0,
	kPollFlagsIn	= 1,
	kPollFlagsPri	= 2,
	kPollFlagsOut	= 4,
	kPollFlagsErr	= 8,
	kPollFlagsHup	= 0x10,
	kPollFlagsNVal	= 0x20,
	kPollFlagsAny	= 0xffffffff
};

inline void operator|=(PollFlags& left, PollFlags right)
{
	left = static_cast<PollFlags>(static_cast<int>(left) | static_cast<int>(right));
}

enum TransmitFileOptions
{
	kTransmitFileOptionsUseDefaultWorkerThread	= 0x00000000,
	kTransmitFileOptionsDisconnect				= 0x00000001,
	kTransmitFileOptionsReuseSocket				= 0x00000002,
	kTransmitFileOptionsWriteBehind				= 0x00000004,
	kTransmitFileOptionsUseSystemThread			= 0x00000010,
	kTransmitFileOptionsUseKernelApc			= 0x00000020,
};

class Socket;

struct PollRequest
{
	int64_t fd;
	PollFlags events;
	PollFlags revents;
};

// TODO: this should really be UNIX_PATH_MAX or SUN_LEN(n)
#define END_POINT_MAX_PATH_LEN	255

#if IL2CPP_COMPILER_MSVC
#pragma warning( push )
#pragma warning( disable : 4200 )
#endif

struct EndPointInfo
{
	AddressFamily family;
	
	union {
		struct {
			uint32_t port;
			uint32_t address;
		} inet;
		char path[END_POINT_MAX_PATH_LEN];
		uint8_t raw[IL2CPP_ZERO_LEN_ARRAY];
	} data;
};

#if IL2CPP_COMPILER_MSVC
#pragma warning( pop ) 
#endif

// NOTE(gab): this must be binary compatible with Windows's WSABUF
struct WSABuf
{
	uint32_t length;
	void *buffer;
};

// NOTE(gab): this must be binary compatible with Window's TRANSMIT_FILE_BUFFERS
struct TransmitFileBuffers
{
	void *head;
	uint32_t head_length;
	void *tail;
	uint32_t tail_length;
};

// Note: this callback can be invoked by the os-specific implementation when an
// interrupt is received or when the native code is looping in a potentially long
// loop.
// If the callback retun false, the executiong of the os-specific method is
// gracefully interrupted, and an error is supposed to be returned by the
// os-specific implementation.
// The callback is allowed to throw exceptions (for example a ThreadAborted exception):
// in this case, it is up to the os-specific implementation to properly deal with
// cleaning up the temporarely allocated memory (if any).
typedef bool (*ThreadStatusCallback)();

class Socket : public il2cpp::utils::NonCopyable
{
public:
	
	Socket (ThreadStatusCallback thread_status_callback);
	~Socket ();
	
	// Note: this Create is only used internally
	WaitStatus Create (int64_t fd, int32_t family, int32_t type, int32_t protocol);
	WaitStatus Create (AddressFamily family, SocketType type, ProtocolType protocol);

	bool IsClosed ();
	void Close ();

	int64_t GetDescriptor ();
	
	ErrorCode GetLastError () const;
	
	WaitStatus SetBlocking (bool blocking);
	
	WaitStatus Listen (int32_t blacklog);
	
	WaitStatus Bind (const char *path);
	WaitStatus Bind (uint32_t address, uint16_t port);
	WaitStatus Bind (const char *address, uint16_t port);
	WaitStatus Bind (uint8_t address[ipv6AddressSize], uint32_t scope, uint16_t port);
	
	WaitStatus Connect (const char *path);
	WaitStatus Connect (uint32_t address, uint16_t port);
	WaitStatus Connect (uint8_t address[ipv6AddressSize], uint32_t scope, uint16_t port);
	
	WaitStatus Disconnect (bool reuse);
	WaitStatus Shutdown (int32_t how);
	
	WaitStatus GetLocalEndPointInfo (EndPointInfo &info);
	WaitStatus GetRemoteEndPointInfo (EndPointInfo &info);
	
	WaitStatus Receive (const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len);
	WaitStatus Send (const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len);
	
	WaitStatus SendArray (WSABuf *wsabufs, int32_t count, int32_t *sent, SocketFlags c_flags);
	WaitStatus ReceiveArray (WSABuf *wsabufs, int32_t count, int32_t *len, SocketFlags c_flags);
	
	WaitStatus SendTo (uint32_t address, uint16_t port, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len);
	WaitStatus SendTo (const char *path, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len);
	WaitStatus SendTo (uint8_t address[ipv6AddressSize], uint32_t scope, uint16_t port, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len);
	
	WaitStatus RecvFrom (uint32_t address, uint16_t port, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len, os::EndPointInfo &ep);
	WaitStatus RecvFrom (const char *path, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len, os::EndPointInfo &ep);
	WaitStatus RecvFrom (uint8_t address[ipv6AddressSize], uint32_t scope, uint16_t port, const uint8_t *data, int32_t count, os::SocketFlags flags, int32_t *len, os::EndPointInfo &ep);
	
	WaitStatus Available (int32_t *amount);
	
	WaitStatus Accept (Socket **socket);
	
	WaitStatus Ioctl (int32_t command, const uint8_t *in_data, int32_t in_len, uint8_t *out_data, int32_t out_len, int32_t *written);
	
	WaitStatus GetSocketOption (SocketOptionLevel level, SocketOptionName name, uint8_t *buffer, int32_t *length);
	WaitStatus GetSocketOptionFull (SocketOptionLevel level, SocketOptionName name, int32_t *first, int32_t *second);
	
	WaitStatus SetSocketOption (SocketOptionLevel level, SocketOptionName name, int32_t value);
	WaitStatus SetSocketOptionLinger (SocketOptionLevel level, SocketOptionName name, bool enabled, int32_t seconds);
	WaitStatus SetSocketOptionArray (SocketOptionLevel level, SocketOptionName name, const uint8_t *buffer, int32_t length);
	WaitStatus SetSocketOptionMembership (SocketOptionLevel level, SocketOptionName name, uint32_t group_address, uint32_t local_address);
	
	WaitStatus SendFile (const char *filename, TransmitFileBuffers *buffers, TransmitFileOptions options);
	
	static WaitStatus Poll (std::vector<PollRequest> &requests, int32_t timeout, int32_t *result, int32_t *error);
	
	static WaitStatus GetHostName (std::string &name);
	static WaitStatus GetHostByName (const std::string &host, std::string &name, std::vector<std::string> &aliases, std::vector<std::string> &addr_list);
	static WaitStatus GetHostByAddr (const std::string &address, std::string &name, std::vector<std::string> &aliases, std::vector<std::string> &addr_list);
	
	static void Startup ();
	static void Cleanup ();

private:
	SocketImpl* m_Socket;
};

/// Sockets should generally be referenced through SocketHandles for thread-safety.
/// Handles are stored in a table and can be safely used even when the socket has already
/// been deleted.
typedef uint32_t SocketHandle;

enum
{
	kInvalidSocketHandle = 0
};

SocketHandle CreateSocketHandle (Socket* socket);
Socket* AcquireSocketHandle (SocketHandle handle);
void ReleaseSocketHandle (SocketHandle handle);

inline SocketHandle PointerToSocketHandle (void* ptr)
{
	// Double cast to avoid warnings.
	return static_cast<SocketHandle> (reinterpret_cast<size_t> (ptr));
}

/// Helper to automatically acquire and release a Socket within a scope.
struct SocketHandleWrapper
{
	SocketHandleWrapper ()
		: m_Handle (kInvalidSocketHandle)
		, m_Socket (NULL) {}
	SocketHandleWrapper (SocketHandle handle)
		: m_Handle (handle)
	{
		m_Socket = AcquireSocketHandle (handle);
	}
	SocketHandleWrapper (const SocketHandleWrapper& other)
	{
		m_Handle = other.m_Handle;
		if (m_Handle != kInvalidSocketHandle)
			m_Socket = AcquireSocketHandle (m_Handle);
		else
			m_Socket = NULL;
	}
	~SocketHandleWrapper ()
	{
		Release ();
	}

	void Acquire (SocketHandle handle)
	{
		Release ();
		m_Handle = handle;
		m_Socket = AcquireSocketHandle (handle);
	}

	void Release ()
	{
		if (m_Socket)
			ReleaseSocketHandle (m_Handle);
		m_Socket = NULL;
		m_Handle = kInvalidSocketHandle;
	}

	bool IsValid () const
	{
		return (m_Socket != NULL);
	}
	SocketHandle GetHandle () const
	{
		return m_Handle;
	}
	Socket* GetSocket () const
	{
		return m_Socket;
	}
	
	Socket* operator-> () const
	{
		return GetSocket ();
	}
	SocketHandleWrapper& operator= (const SocketHandleWrapper& other)
	{
		Acquire (other.GetHandle ());
		return *this;
	}

private:
	SocketHandle m_Handle;
	Socket* m_Socket;
};

}
}
